4-2 高可用Consul服务+Nestjs微服务高可用问题分析
高可用架构全景
在复杂的微服务部署中,高可用涉及两个层面:
┌─────────────────────────────────────────────────────┐
│ 微服务客户端侧(Gateway) │
│ │
│ ConsulClients ──→ gRPC Clients │
│ (服务发现客户端) (业务调用客户端) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ ConsulClient1│ │ ConsulClient2│ │
│ │ (DC-Beijing) │ │ ConsulClient2│ │
│ └──────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ gRPC Clients Map │ │
│ │ serviceName → [instance1, ...] │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 微服务服务端侧 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ DataCenter 1 │ │ DataCenter 2 │ │
│ │ (Beijing) │ │ (Shanghai) │ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │Consul │ │ │ │Consul │ │ │
│ │ │Server │◄─┼────┼─►│Server │ │ │
│ │ └────────┘ │ │ └────────┘ │ │
│ │ user-service│ │ user-service│ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
text
两层 Client 架构
第一层:ConsulClients
负责连接不同的 Consul Data Center,获取服务实例列表:
| 职责 | 说明 |
|---|---|
| 服务发现 | 从 Consul Server 获取健康服务实例 |
| DC 容灾 | 某个 Data Center 不可用时切换到另一个 |
| 配置同步 | Consul 自身负责 DC 间的数据同步 |
第二层:gRPC Clients
负责实际的业务调用,每个服务名对应一组实例:
// 数据结构示例
const grpcClients = new Map<string, ServiceInstance[]>();
grpcClients.set('user-service', [
{ address: '192.168.1.100', port: 40001, healthy: true },
{ address: '192.168.1.101', port: 40001, healthy: true },
]);
typescript
客户端侧负载均衡
策略一:随机选择
每次请求从健康实例列表中随机选取一个:
getHealthyInstance(serviceName: string) {
const instances = this.grpcClients.get(serviceName)
.filter(i => i.healthy);
return instances[Math.floor(Math.random() * instances.length)];
}
typescript
策略二:权重分配
为不同实例配置流量比例:
// .env 中配置权重
USER_SERVICE_WEIGHTS=instance1:70,instance2:30
// 解析权重后按比例分配请求
typescript
策略三:轮询
依次使用每个健康实例:
private roundRobinIndex = new Map<string, number>();
getHealthyInstance(serviceName: string) {
const instances = this.grpcClients.get(serviceName);
const index = (this.roundRobinIndex.get(serviceName) || 0) % instances.length;
this.roundRobinIndex.set(serviceName, index + 1);
return instances[index];
}
typescript
拦截器高可用策略
当 gRPC 调用出现异常时,拦截器有三种处理方案:
方案一:Controller 装饰器
在 Controller 上通过装饰器标注关联的微服务,拦截器根据装饰器信息更新对应的 gRPC Client:
@Microservice(['user-service', 'order-service'])
@Controller('users')
export class UserController {}
typescript
缺点:需要修改代码,不够灵活。
方案二:请求头传递
前端在 HTTP Header 中标注需要的微服务:
// 前端请求
fetch('/api/users', {
headers: { 'X-Microservices': 'user-service,order-service' }
});
typescript
缺点:前端需要知道后端的微服务拆分,耦合度高。
方案三:定时更新 + 重试(推荐)
拦截器只负责:捕获异常 → 重试请求
全局定时器负责:定期更新所有 gRPC Clients 的健康实例列表
两者解耦,互不依赖
text
优势:
| 优势 | 说明 |
|---|---|
| 解耦 | 拦截器和 ConsulService 无直接关联 |
| 通用 | 一个拦截器适用于所有微服务 |
| 灵活 | 定时更新频率可配置 |
| 简单 | 不需要装饰器或请求头 |
架构演进方向
当前架构:
Gateway → ConsulService → 单个 gRPC Client
目标架构:
Gateway → ConsulServiceManager
├── ConsulService1 (user-service)
│ └── gRPC Client Map: [instance1, instance2]
├── ConsulService2 (order-service)
│ └── gRPC Client Map: [instance1]
└── 定时更新所有服务实例
text
核心模块
| 模块 | 职责 |
|---|---|
| ConsulServiceManager | 管理多个 ConsulService 实例 |
| ConsulService | 单个服务的发现、健康检查、Client 管理 |
| GrpcExceptionInterceptor | 捕获异常、触发重试 |
| HealthUpdateScheduler | 定时更新所有服务的健康实例列表 |
参考资源
- Consul Architecture - Consul 架构
- NestJS Microservices - 微服务基础
- gRPC Load Balancing - gRPC 负载均衡
↑